# middleware.py
import asyncio
import inspect
import json
import logging
import time
from datetime import datetime
from typing import Any, Callable, Dict, Optional, TypeVar

from fastapi import Request
from jose import JWTError, jwt
from sqlalchemy.exc import SQLAlchemyError
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.responses import Response
from user_agents import parse

from app.core import settings
from app.database.db import auto_commit_session
from app.models.system import SysLog
from app.utils.ip_utils import get_client_ip, get_ip_geolocation_info

logger = logging.getLogger(__name__)

F = TypeVar("F", bound=Callable[..., Any])

# 常量定义
SENSITIVE_KEYS = ["password", "token", "secret", "key"]
MAX_PARAMS_LENGTH = 1000
MAX_RESPONSE_LENGTH = 2000
MAX_ERROR_RESPONSE_LENGTH = 500


def __decode_token(token: str, verify_flag: bool = False) -> dict:
    """
    解码JWT令牌

    Args:
        token: JWT令牌字符串
        verify_flag: 是否验证签名，默认为False

    Returns:
        dict: 解码后的payload字典，解码失败返回空字典
    """
    try:
        payload = jwt.decode(
            token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM], options={"verify_exp": verify_flag}
        )
        return payload
    except JWTError:
        return {}


async def save_log_to_db(log_data: dict) -> None:
    """
    将日志保存到数据库（后台任务函数）

    Args:
        log_data: 日志数据字典

    Note:
        后台任务中的错误会被记录但不影响主流程
    """
    try:
        async with auto_commit_session() as log_session:
            log_entry = SysLog(**log_data)
            log_session.add(log_entry)
    except Exception as e:
        logger.error(f"后台保存日志失败: {e}")


def _filter_sensitive_data(data: dict) -> dict:
    """
    过滤敏感信息

    Args:
        data: 需要过滤的数据字典

    Returns:
        dict: 过滤后的数据字典
    """
    if not isinstance(data, dict):
        return data

    filtered_data = data.copy()
    for key in SENSITIVE_KEYS:
        if key in filtered_data:
            filtered_data[key] = "***"
    return filtered_data


def _truncate_string(content: str, max_length: int) -> str:
    """
    截断字符串到指定长度

    Args:
        content: 原始字符串
        max_length: 最大长度

    Returns:
        str: 截断后的字符串
    """
    if len(content) > max_length:
        return content[:max_length] + "..."
    return content


async def _build_safe_request_params(request: Request) -> Optional[str]:
    """
    构建安全的请求参数字符串（区分GET和非GET方法）

    Args:
        request: FastAPI 请求对象

    Returns:
        Optional[str]: 安全的请求参数字符串，处理失败返回None
    """
    try:
        params = await _safely_collect_request_params(request)
        params_str = str(params)
        return _truncate_string(params_str, MAX_PARAMS_LENGTH)
    except Exception as e:
        logger.warning(f"构建请求参数失败: {e}")
        return None


async def _safely_collect_request_params(request: Request) -> Dict[str, Any]:
    """
    安全地收集请求参数，不影响请求体

    Args:
        request: FastAPI 请求对象

    Returns:
        Dict[str, Any]: 包含各类参数的字典
    """
    params = {}

    # 1. 查询参数（所有方法都可能有）
    if request.query_params:
        query_params = dict(request.query_params)
        params["query"] = _filter_sensitive_data(query_params)

    # 2. 路径参数（从 URL 路径中获取）
    if hasattr(request, "path_params") and request.path_params:
        params["path"] = dict(request.path_params)

    # 3. 请求头（排除敏感信息）
    headers = dict(request.headers)
    # 移除敏感头信息
    sensitive_headers = ["authorization", "cookie", "proxy-authorization", "x-api-key"]
    for sensitive in sensitive_headers:
        headers.pop(sensitive, None)
    params["headers"] = headers

    # 4. 根据HTTP方法处理请求体
    method = request.method.upper()

    if method == "GET":
        # GET方法只有查询参数和路径参数
        pass
    else:
        # 非GET方法：POST、PUT、DELETE等
        content_type = request.headers.get("content-type", "").lower()

        if "application/json" in content_type:
            # 对于JSON数据，安全地尝试读取但不消耗
            params["body"] = await _safely_extract_json_body(request)

        elif "application/x-www-form-urlencoded" in content_type:
            # 对于表单数据，安全处理
            params["form"] = await _safely_extract_form_data(request)

        elif "multipart/form-data" in content_type:
            # 对于文件上传，只记录元数据
            params["form"] = await _safely_extract_multipart_data(request)

        else:
            # 其他内容类型，记录基本信息
            content_length = request.headers.get("content-length")
            if content_length:
                params["body_info"] = f"content-type: {content_type}, length: {content_length}"

    return params


async def _safely_extract_json_body(request: Request) -> Dict[str, Any]:
    """
    安全地提取JSON请求体，不消耗原始请求体

    Args:
        request: FastAPI 请求对象

    Returns:
        Dict[str, Any]: 过滤后的JSON数据或错误信息
    """
    # 保存原始的_receive方法
    original_receive = getattr(request, "_receive", None)

    try:
        # 检查是否已经读取过body
        if hasattr(request, "_body") and request._body:
            # 如果已经读取过，使用缓存的body
            body_bytes = request._body
        else:
            # 读取body但不消耗
            body_bytes = await request.body()

            # 临时替换_receive方法以返回缓存的body
            async def receive():
                return {"type": "http.request", "body": body_bytes}

            request._receive = receive

        if body_bytes:
            try:
                body_data = json.loads(body_bytes.decode("utf-8"))
                return _filter_sensitive_data(body_data)
            except json.JSONDecodeError:
                return {"error": "Invalid JSON format"}
            except UnicodeDecodeError:
                return {"error": "Invalid encoding"}

    except Exception as e:
        return {"error": f"Failed to extract JSON: {str(e)}"}
    finally:
        # 确保在所有情况下都恢复原始的_receive方法
        if original_receive is not None:
            request._receive = original_receive

    return {}


async def _safely_extract_form_data(request: Request) -> Dict[str, Any]:
    """
    安全地提取表单数据

    Args:
        request: FastAPI 请求对象

    Returns:
        Dict[str, Any]: 表单数据或错误信息
    """
    try:
        form_data = await request.form()
        form_dict = {}
        for key, value in form_data.items():
            form_dict[key] = str(value)

        return _filter_sensitive_data(form_dict)
    except Exception as e:
        return {"error": f"Failed to extract form data: {str(e)}"}


async def _safely_extract_multipart_data(request: Request) -> Dict[str, Any]:
    """
    安全地提取multipart表单数据（文件上传）

    Args:
        request: FastAPI 请求对象

    Returns:
        Dict[str, Any]: 文件元数据信息
    """
    try:
        form_data = await request.form()
        form_dict = {}
        for key, value in form_data.items():
            if hasattr(value, "filename"):  # 文件字段
                form_dict[key] = {
                    "type": "file",
                    "filename": getattr(value, "filename", "unknown"),
                    "size": getattr(value, "size", "unknown"),
                    "content_type": getattr(value, "content_type", "unknown"),
                }
            else:
                form_dict[key] = str(value)

        return _filter_sensitive_data(form_dict)
    except Exception as e:
        return {"error": f"Failed to extract multipart data: {str(e)}"}


def _build_safe_response_content(response: Any) -> Optional[str]:
    """
    构建安全的响应内容字符串

    Args:
        response: 响应对象

    Returns:
        Optional[str]: 安全的响应内容字符串
    """
    try:
        # Pydantic v2 兼容处理
        if hasattr(response, "model_dump") and callable(getattr(response, "model_dump")):
            content = response.model_dump()
        elif hasattr(response, "dict") and callable(getattr(response, "dict")):
            content = response.dict()
        elif hasattr(response, "__dict__"):
            content = response.__dict__
        else:
            content = str(response)

        # 过滤敏感信息并限制长度
        if isinstance(content, dict):
            content = _filter_sensitive_data(content)

        content_str = str(content)
        return _truncate_string(content_str, MAX_RESPONSE_LENGTH)
    except Exception:
        return _truncate_string(str(response), MAX_ERROR_RESPONSE_LENGTH) if response else None


def _parse_user_agent(user_agent: str) -> tuple[str, str, str]:
    """
    解析浏览器用户代理字符串

    Args:
        user_agent: 用户代理字符串

    Returns:
        tuple: (浏览器名称, 浏览器版本, 操作系统)
    """
    parsed_agent = parse(user_agent)
    browser = parsed_agent.browser.family or "Unknown"
    browser_version = parsed_agent.browser.version_string or "Unknown"
    os = parsed_agent.os.family or "Unknown"
    return browser, browser_version, os


def _get_user_info_from_request(request: Request) -> Optional[int]:
    """
    从请求中获取用户信息

    Args:
        request: FastAPI 请求对象

    Returns:
        Optional[int]: 用户ID，未找到返回None
    """
    auth_header = request.headers.get("Authorization")
    if not auth_header:
        return None

    token = auth_header.replace("Bearer ", "")
    payload = __decode_token(token) if token else {}
    return payload.get("user_id")


def _get_route_info(request: Request) -> tuple[str, str, str]:
    """
    从请求中获取路由信息

    Args:
        request: FastAPI 请求对象

    Returns:
        tuple: (模块名, 函数名, 日志内容)
    """
    # 获取路由路径
    path = request.url.path

    # 从路径中提取模块名（第一个路径段）
    module = path.split("/")[1] if len(path.split("/")) > 1 else "unknown"

    # 函数名使用路径的最后部分或整个路径
    func_name = (
        path.split("/")[-1] if path.split("/")[-1] else path.split("/")[-2] if len(path.split("/")) > 1 else "unknown"
    )

    # 日志内容基于请求方法和路径
    content = f"{request.method} {path}"

    return module, func_name, content


async def _prepare_log_data(
    request: Request,
    content: str,
    module: str,
    func_name: str,
    execution_time: int,
    response: Any = None,
    is_error: bool = False,
    error_message: str = None,
) -> dict:
    """
    准备日志数据字典

    Args:
        request: 请求对象
        content: 日志内容
        module: 模块名称
        func_name: 函数名称
        execution_time: 执行时间（毫秒）
        response: 响应对象（可选）
        is_error: 是否为错误日志
        error_message: 错误信息（可选）

    Returns:
        dict: 完整的日志数据字典
    """
    # 获取客户端 IP 和地理位置
    client_ip = get_client_ip(request)
    location_info = {}

    if client_ip:
        try:
            location_info = get_ip_geolocation_info(client_ip)
        except Exception:
            pass  # 地理位置查询失败不影响主流程

    province = location_info.get("province", "Unknown")
    city = location_info.get("city", "Unknown")

    # 解析浏览器信息
    user_agent = request.headers.get("User-Agent", "")
    browser, browser_version, os = _parse_user_agent(user_agent)

    # 获取用户信息
    user_id = _get_user_info_from_request(request)

    # 构建请求和响应内容（使用新的异步参数收集方法）
    request_params = await _build_safe_request_params(request)
    response_content = None

    if not is_error:
        response_content = _build_safe_response_content(response)

    # 准备完整的日志数据
    log_content = content if not is_error else f"{content} - 错误: {error_message}"
    final_response_content = response_content if not is_error else f"错误: {error_message}"

    return {
        "module": module,
        "request_method": request.method,
        "content": log_content,
        "request_params": request_params,
        "response_content": final_response_content,
        "request_uri": str(request.url),
        "method": func_name,
        "ip": client_ip,
        "province": province,
        "city": city,
        "execution_time": execution_time,
        "browser": browser,
        "browser_version": browser_version,
        "os": os,
        "create_by": user_id,
        "create_time": datetime.now(),
        "is_deleted": 0,
    }


def _create_log_task(log_data: dict) -> asyncio.Task:
    """
    创建后台日志保存任务

    Args:
        log_data: 日志数据字典

    Returns:
        asyncio.Task: 后台任务对象
    """
    return asyncio.create_task(save_log_to_db(log_data))


def _extract_function_parameters(func: F, kwargs: dict) -> dict:
    """
    提取函数参数，排除装饰器内部使用的参数

    Args:
        func: 目标函数
        kwargs: 原始关键字参数

    Returns:
        dict: 过滤后的参数字典
    """
    sig = inspect.signature(func)
    parameters = sig.parameters

    return {param_name: kwargs[param_name] for param_name in parameters if param_name in kwargs}


class APILoggingMiddleware(BaseHTTPMiddleware):
    """
    API日志记录中间件

    功能：
    - 记录所有API请求的详细信息
    - 自动过滤敏感信息
    - 异步保存日志到数据库
    - 支持错误和成功日志分离
    """

    async def dispatch(self, request: Request, call_next) -> Response:
        """
        处理HTTP请求并记录日志

        Args:
            request: HTTP请求对象
            call_next: 调用下一个中间件或路由的函数

        Returns:
            Response: HTTP响应对象

        Raises:
            Exception: 当请求处理过程中发生错误时抛出
        """
        start_time = time.time()

        # 获取路由信息
        module, func_name, content = _get_route_info(request)

        try:
            # 执行请求处理
            response: Response = await call_next(request)

            # 计算执行时间
            execution_time = int((time.time() - start_time) * 1000)

            # 准备日志数据（成功情况）
            log_data = await _prepare_log_data(
                request=request,
                content=content,
                module=module,
                func_name=func_name,
                execution_time=execution_time,
                response=response,
                is_error=False,
            )

            # 创建后台日志保存任务
            _create_log_task(log_data)

            return response

        except Exception as e:
            # 计算执行时间
            execution_time = int((time.time() - start_time) * 1000)

            # 准备日志数据（错误情况）
            log_data = await _prepare_log_data(
                request=request,
                content=f"{content} - 执行失败",
                module=module,
                func_name=func_name,
                execution_time=execution_time,
                response=None,
                is_error=True,
                error_message=str(e),
            )

            # 创建后台日志保存任务
            _create_log_task(log_data)

            # 重新抛出异常以保持正常的错误处理流程
            raise e
